#include <ConnectionBroker/clientconnection/LongPollConnection.h>
#include <ConnectionBroker/clientconnection/LongPollClientConnection.h>
#include <ConnectionBroker/server/LongPollServer.h>
#include <ConnectionBroker/PacketUtils.h>
#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/lexical_cast.hpp>
#include <json/json.h>

using namespace ConnectionBroker;

LongPollConnection::LongPollConnection(boost::asio::io_service& pIoService, LongPollServer* pServer)
: mSocket(pIoService), mIoService(pIoService), mServer(pServer), mStrand(pIoService)
{
}

LongPollConnection::~LongPollConnection()
{
}

void LongPollConnection::start()
{
   mSocket.async_read_some(
        boost::asio::buffer(mBuffer),
        mStrand.wrap(
            boost::bind(
            &LongPollConnection::handleRead, shared_from_this(),
            boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
}

void LongPollConnection::handleRead(const boost::system::error_code& pError, std::size_t pBytesTransferred)
{
   if(pError)
      return;

   boost::tribool result;
   boost::tie(result, boost::tuples::ignore) = mRequestParser.parse(mRequest, mBuffer.data(), mBuffer.data() + pBytesTransferred);
   if(!result)
   {
      return;
   }
   else if(result)
   {
      if(!handleRequest(mRequest, mReply))
         return;

      boost::asio::async_write(mSocket, mReply.to_buffers(),
         mStrand.wrap(
            boost::bind(&LongPollConnection::handleWrite, shared_from_this(), boost::asio::placeholders::error)));
   }
   else
   {
      mSocket.async_read_some(
         boost::asio::buffer(mBuffer),
         mStrand.wrap(
            boost::bind(
               &LongPollConnection::handleRead, shared_from_this(),
               boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
   }
}

void LongPollConnection::fillReply(const std::string& msgStr, HttpReply& rep)
{
   rep.content = msgStr;
   rep.status = HttpReply::ok;
   rep.headers.resize(2);
   rep.headers[0].name = "Content-Length";
   rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
   rep.headers[1].name = "Content-Type";
   rep.headers[1].value = "text/plain";
}

bool LongPollConnection::handleRequest(const HttpRequest& req, HttpReply& rep)
{
   bool is_get = !req.method.compare("GET") || !req.method.compare("OPTIONS");
   bool is_post = !req.method.compare("POST");

   if((!(req.uri.compare("/connect")) || !(req.uri.compare("/connect?"))) && is_get)
   {
      LongPollClientConnectionPtr pNewClient = mServer->longPollClientFactory();
      //Send back client id and password
      int newId = pNewClient->id();
      std::string newPass(pNewClient->pass());
      Json::Value msgJson(Json::objectValue);
      msgJson["connectionId"] = newId;
      msgJson["password"] = newPass;
      Json::FastWriter writer;
      fillReply(writer.write(msgJson), rep);
      return true;
   }

   std::vector<std::string> strs;
   boost::split(strs, req.uri, boost::is_any_of("/"));

   if(strs.size() != 6)
      return false;

   if(strs[2].compare("connectionId") != 0 || strs[4].compare("password") != 0)
      return false;

   int connectionId = atoi(strs[3].c_str());
   std::string password(strs[5].c_str());

   if(!connectionId || password.empty())
      return false;

   LongPollClientConnectionPtr pClient = mServer->idPassToClient(connectionId, password);
   if(!pClient)
      return false;

   if(!(strs[1].compare("updates")) && is_get)
   {
      std::vector<Packet> messages(pClient->getMessages());
      std::string msgStr(PacketUtils::packetsToString(messages));
      fillReply(msgStr, rep);

      pClient->reset();
      return true;
   }

   else if(!(strs[1].compare("message")) && is_post)
   {
      if(req.content.empty())
         return false;

      mServer->messageReceived(pClient, req.content);
      pClient->reset();
      std::string msgStr(PacketUtils::phpOkToString());
      fillReply(msgStr, rep);
      return true;
   }

   return false;
}

void LongPollConnection::handleWrite(const boost::system::error_code& e)
{
   //If there's an error, just return and the connection will get cleaned up automatically.
   if(e)
      return;

   boost::system::error_code ignored_ec;
   mSocket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
}
